/* ----------------------------------------------------------------------------

	pax.widget.js Copyright (C) 2006, 2007, 2008 Mikkel Bergmann, Pointful

	Licence
	-------
	
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

	See lgpl.txt for licence details

---------------------------------------------------------------------------- */
/*
	Script: pax.widget
		Base widget class for all PAX widgets
		
	Author:
		Mikkel Bergmann, <http://www.pointful.com>

*/


var pax = pax || {};
pax.widget = pax.widget || {};
pax.widget.ajax = pax.widget.ajax || {};

/*	Property: pax.widget.uniqueId
		Unique identifiers counter for widgets	*/
pax.widget.uniqueId = 0;


/*	Method: pax.widget.init
	Initialises a PAX widget
	
	Parameters:
		args - Object with widget functionality
		* model - Model used by the associated widget template
		* paxWidgetId - Optionally specify a unique ID that can be used to identify an instance of a widget in the DOM (This is auto generated if not specified)
		* template - PAX Template (string) to render
		* target - Element to output the widget to
		
	Returns:
		Object with model and various widget function.
		
	Example:
		(start code)
			<div style='border: 1px solid grey' id='widget_output'></div>
			[:.
				var myWidget = pax.widget.init( {
					model: {
						text: 'Hello, this is my first widget!'
					},
					template: 'Your text is: "[:= text :]".',
					target: pax.$('widget_output')
				} );
				
				myWidget.render();
			:]
		(end)
	This would show the template rendered in the target. See <pax.template> for template details.

*/
pax.widget.init = function( args ) {
	pax.widget.uniqueId += 1;	// The unique ID can be used to identify this widget

	var myWidget = pax.defaultArgs( {						// Widget object default required args
		model: {},											// Model used by the template
		paxWidgetId: 'paxWidget' + pax.widget.uniqueId,		// Unique ID you can optionally used to identify an instance of a widget in the DOM
		template: 'empty template',							// PAX Template (string form) to render
		templateURL: '',									// Optional URL to load a template from
		target: document.body,								// Bad, but what else could we use? At least it will be noticed ;o)
		bindQueue: [],										// Queue for the bound objects.
		widgetQueue: [],									// Queue for widgets used (so we can call destroy() on them)
		bindElements: {},									// Elements we want to bind using the widget binding methods
		
		//	Keeps track of widgets that are used within this widget.
		useWidget: function( widget ) {
			this.widgetQueue.push( widget );
		},

		//	Adds a bound object to the bind queue
		addToBindQueue: function( bindObj ) {
			this.bindQueue.push( bindObj );
		},

		//	Removes a bound object from the bind queue
		removeFromBindQueue: function( bindObj ) {
			for( var i = 0; i < this.bindQueue.length; i++ ) {
				if( this.bindQueue[i].obj == bindObj.obj && this.bindQueue[i].event == bindObj.event )this.bindQueue.splice(i, 1);
			}
		},

		//	Pass-through method for bind, we keep a seperate queue for this widget.
		bind: function( obj, event, func ) {
			this.addToBindQueue( pax.event.bind( obj, event, func ) );
		},

		//	Pass-through method for unbind, remove bound obj from queue for this widget.
		unbind: function( obj, event, func ) {
			this.removeFromBindQueue( { obj: obj, event: event, func: func } );
			pax.event.unbind( obj, event, func );
		},

		//	Pass-through method for bindOne
		bindOne: function( obj, event, func ) {
			this.addToBindQueue( pax.event.bindOne( obj, event, func ) );
		},

		onBeforeDestroy: function( widget) {	// You can override this to do *something* before destroy is executed.
		},

		_destroy: function() {	// Cleans up the DOM events, and destroys any registered widgets. If you override this, don't forget to run onBeforeDestroy
			this.onBeforeDestroy( this );
			for( var w = 0; w < this.widgetQueue.length; w++ )this.widgetQueue[w].destroy();
			for( var b = 0; b < this.bindQueue.length; b++ )this.unbind( this.bindQueue[b].obj, this.bindQueue[b].event, this.bindQueue[b].func );
			pax.event.unbind( this );
		},
		
		//	You can override this method with your own destroy, just remember to call _destroy somewhere
		destroy: function() {
			this._destroy();
		},

		getElements: function( target, selector, className ) {
			//	We could optimise here, by caching all elements before traversing
			return pax.util.getElementsByClassName( target, selector, className );
		},
		
		/*	Method: bindElements.classNames
			This will bind functions, by using the class names that you specify in the <pax.widget.init> method, through the bindElements object
			
			Parameters:
				args - Object with CSS class name as key, and functions as values
				
			Returns:
				Not applicable
				
			Note:
				This is an internal method to the pax.widget system, and can only be accessed through the init methods.
			
			Example:
				(start code)
					pax.widget.init(
						target: target, 
						model: model, 
						template: args.template,
						bindElements: {
							classNames: {
								'backButtonCSSClass': {
									click: function( e ) {
										if( model.offset > 0 ) {
											model.offset -= 1;
											model.widget.render();
										}
									}
								}
							}
						}
					);
				(end)
				This example shows how to access the classNames binding through the <pax.widget.init> method. 'backButtonCSSClass' is the CSS class on 
				the button you want to attach the events to, in this case we have a 'click' event to attach to the button.
		*/
		bindClassNames: function( target, args ) {
			for( var className in args ) {
				//	This is VERY slow in IE, for a large set of elements, use the bindOne pattern method instead
				var elements = this.getElements( target, '*', className );
				
				for( var i = 0; i < elements.length; i++ ) {
					var item = elements[i];
					var funcOnly = ( typeof(args[className]) == 'function' );
					// if( funcOnly )this.bindQueue.push( pax.event.bindOne( item, 'click', args[className] ) );
					if( funcOnly )this.bindOne( item, 'click', args[className] );
					else {
						for( var evt in args[className] ) {
							if( typeof( args[className][evt] ) == 'function' )this.bindOne( item, evt, args[className][evt] );
						}
					}
				}
			}
		},
		
		unbindClassNames: function( target, args ) {	// This unbinds functions, by using the class names
			for( var className in args ) {
				var elements = this.getElements( target, '*', className );
				for( var i = 0; i < elements.length; i++ ) {
					var item = elements[i];
					for( var evt in args[className] ) {
						//	pax.event.unbind( item, evt, args[className][evt] );
						this.unbind( item, evt, args[className][evt] );
					}
				}
			}
		},
		
		
		/*	Method: bindElements.id
			This will bind functions, by using the element ids that you specify in the <pax.widget.init> method, through the bindElements object
			
			Parameters:
				args - Object with element ids as keys, and functions as values
				
			Returns:
				Not applicable
				
			Note:
				This is an internal method to the pax.widget system, and can only be accessed through the init methods.
			
			Example:
				(start code)
					pax.widget.init(
						target: target, 
						model: model, 
						template: args.template,
						bindElements: {
							id: {
								'myButtonID': {
									click: function( e ) {
										alert('You clicked on the button!');
									}
								}
							}
						}
					);
				(end)
				This example shows how to access the id binding through the <pax.widget.init> method. 'myButtonID' is the element ID of the button you 
				want to attach the events to, in this case we have a 'click' event to attach to the button.
		*/
		bindIds: function( target, args ) {			// This binds functions by using element IDs
			for( var id in args ) {
				for( var evt in args[id] ) {
					this.bind( pax.$(id), evt, args[id][evt] );
				}
			}
		},
		unbindIds: function( target, args ) {			// This unbinds functions by using element IDs
			for( var id in args ) {
				for( var evt in args[id] ) {
					this.unbind( pax.$(id), evt, args[id][evt] );
				}
			}
		},
		
		/*	Method: bindElements.internal
			This will bind functions, by using the element id + _target id that you specify in the <pax.widget.init> method, through the bindElements object.
			The purpose of this method is to semi-automatically provide a way to have unique elements for instances of the same widget.
			
			Parameters:
				args - Object with element id as keys, and functions as values
				
			Returns:
				Not applicable
				
			Note:
				This is an internal method to the pax.widget system, and can only be accessed through the init methods.
			
			Example:
				(start code)
					//	Assume that you have a widget where you have defined a button in the template like so:
					//		<input type='button' id='forward_[:= model.target.id :]' value = 'Forward'/>
					pax.widget.init(
						target: target, 
						model: model, 
						template: args.template,
						bindElements: {
							internal: {
								'forward': {
									click: function( e ) {
										alert('You clicked on the forward button!');
									}
								}
							}
						}
					);
				(end)
				This example shows how to access the internal binding through the <pax.widget.init> method. The 'forward' key referes to an element that is 
				defined as "forward_" + model.target.id
		*/
		internalBindIds: function( target, args ) {	// This binds functions by using element IDs post-fixed with '_' + target.id
			for( var id in args ) {
				for( var evt in args[id] ) {
					if( pax.$(id + '_' + target.id) ) {
						this.bind( pax.$(id + '_' + target.id), evt, args[id][evt] );
					}
				}
			}
		},
		internalUnbindIds: function( target, args ) {	// This unbinds functions by using element IDs post-fixed with '_' + target.id
			if (target) {
				for (var id in args) {
					for (var evt in args[id]) {
						if (pax.$(id + '_' + target.id)) 
							this.unbind(pax.$(id + '_' + target.id), evt, args[id][evt]);
					}
				}
			}
		},
		bindOneEvent: function( target, args ) {			// Binds one function to the target, for each event type in the args. This is MUCH faster if we have a lot of classes
			var evtDict = {};
			
			// Create dict with event as key, and value is an object with className -> function to run.
			for( var className in args ) {
				for( var evt in args[className] ) {
					evtDict[evt] = (typeof(evtDict[evt]) == 'undefined' )? {}: evtDict[evt];
					evtDict[evt][className] = args[className][evt];
				}
			}
			
			// Bind the events
			for( var elementEvt in evtDict ) {
				if( elementEvt != 'prototype' ) {
					this.bindOne( target, elementEvt, function(e) {
						if( pax.util.hasKey( e.type, evtDict ) ) {
							for( var className in evtDict[e.type] ) {
								var element = e.target || window.event.srcElement;
								/*
									We don't get event bubbeling here, as we have captured the click event...
										- This means we only get the inner most click element...
										- We must handle that ourselves, as we have over-ridden the event management
										- Perhaps iterate through all parent elements of the target element?
											. This *may* need to be reursive, though element.parentElements should be enough...
								*/
								// Loop on element.parentNode until we reach target
								var foundElement;
								while( element != target ) {
									var elx = pax.$( element );
									foundElement = false;
									//	Check element first
									if( pax.util.hasClassName( elx, className ) ) {
										//	Run the function, with the orignal event, and the target we found
										evtDict[e.type][className].apply( elx, [e, elx] );
										foundElement = true;
										break;
									}
										
									//	Then check child nodes
									for( var x = 0; x < element.childNodes.length; x++ ) {
										var elx = pax.$( element.childNodes[x] );
										if( pax.util.hasClassName( elx, className ) ) {
											//	Run the function, with the orignal event, and the target we found
											evtDict[e.type][className].apply( elx, [e, elx] );
											foundElement = true;
											break;
										}
										if( foundElement )break;
									}
									if( foundElement )break;
									element = element.parentNode;
								}
							}
						}
					} );
				}
			}
		},
		
		render: function() {
			//	Check if template is a function, and run it first, if so...
			var tpl = ( pax.util.getType( this.template ) == 'function' )? this.template(): this.template;
			if( pax.util.getType( this.beforeRender ) == 'function' )this.beforeRender( this );
			
			//	Automatically unbind events
			this.internalUnbindIds( this.model.target, this.bindElements.internal );
			this.unbindClassNames( this.model.target, this.bindElements.className );
			this.unbindIds( this.model.target, this.bindElements.id );
			
			pax.template.render( tpl, { target: this.target, value: this.model } );
			
			if( pax.util.getType( this.afterRender ) == 'function' )this.afterRender( this );
			
			//	Automatically bind events
			this.internalBindIds( this.model.target, this.bindElements.internal );
			this.bindClassNames( this.model.target, this.bindElements.className );
			this.bindIds( this.model.target, this.bindElements.id );
		}
	}, args );
	
	//	Internal reference
	myWidget.model.widget = myWidget;
	
	return myWidget;
};



//	Returns an internal element.
pax.widget.getInternalElement = function(id, target) {
	return pax.$(id + '_' + target.id);
};

//	Returns an internal ID.
//	TODO: Allow using an internal pax ID, if the target doesn't have an ID.
pax.widget.getInternalID = function(id, target) {
	return id + '_' + target.id;
};


/*	"behaviour" style init, useful for initting by setting a class on elements, and then runnig this after load.	*/
pax.widget.initByClassName = function( element, elementType, className, model ) {
	// var elements = pax.util.getElementsByClassName( element, elementType, className );
	var elements = this.getElements( element, elementType, className );
	for( var i = 0; i < elements.length; i++ ) {
		pax.widget.init( { target: elements[i], model: model } );
	};
};


/*	Method: pax.widget.ajax.init
	This creates an ajax based widget based on the widget object. ie: it adds a controller, and a server request method.
*/
/*	Method: pax.widget.ajax.init
	This creates an ajax based widget, using pax.widget.init as a base widget object. ie: it adds an ajax controller, and a server request method.
	
	Parameters:
		args - Object with widget functionality
		* url - The URL that the widget sends it's requests to.
		* model - Model used by the associated widget template
		* paxWidgetId - Optionally specify a unique ID that can be used to identify an instance of a widget in the DOM (This is auto generated if not specified)
		* template - PAX Template (string) to render
		* templateURL - Optional URL to load a template from
		* target - Element to output the widget to.
		
	Returns:
		Object with model and various widget function.
		
	Example:
		(start code)
			<div style='border: 1px solid grey' id='ajax_widget_output'></div>
			[:.
				pax.widget.ajax.init( '../examples/widget_introduction_example3.txt', {
				    target: pax.$('ajax_widget_output')
				} ).serverRequest();
			:]
		(end)
	This would show the template rendered in the target. See <pax.template> for template details.
	Also, be sure to read the widget introduction article!
*/
pax.widget.ajax.init = function( url, args ) {
	//	Create widget object, and extend with args and default ajax functionality
	//	var myAjaxWidget = pax.defAddArgs( pax.widget.init( args ), pax.defaultArgs( {
	var myAjaxWidget = pax.defAddArgs( pax.widget.init( args ), pax.defAddArgs( {
		url: url,						// Data loading URL
		model: { param: {} },			// Parameters to pass to the url
		serverRequest: function() {		// This method sends a request to the server, using this.param as the post string
			if( this.templateURL != '' ) {
				var loadData = function() {
					pax.post( this.url, pax.postString( this.model.param ), this.controller, 'pax.widget.serverRequest posted' );
				};
				pax.cache.template.loadTemplate( this.templateURL, false, loadData );
			} else {
				pax.post( this.url, pax.postString( this.model.param ), this.controller, 'pax.widget.serverRequest posted' );
			}
		},
		controller: function( xml, txt, url ) {									// This is the default callback handle function
			var response = pax.unJSON( txt );									//	Unmarshal the response
			var template = args.template || "[:= response :]";					//	Default template
			var value = pax.defAddArgs( { response: response }, myAjaxWidget );	//	Add in the widget
			if( response && response != '' ) {									//	Show response, by rendering template
			
				if( pax.util.getType( myAjaxWidget.beforeRender ) == 'function' )myAjaxWidget.beforeRender( myAjaxWidget );
				
				//	Automatically unbind events
				myAjaxWidget.internalUnbindIds( myAjaxWidget.model.target, myAjaxWidget.bindElements.internal );
				myAjaxWidget.unbindClassNames( myAjaxWidget.model.target, myAjaxWidget.bindElements.className );
				myAjaxWidget.unbindIds( myAjaxWidget.model.target, myAjaxWidget.bindElements.id );
				
				pax.template.render( template, {
					value: value,
					target: args.target 
				} );
				
				if( pax.util.getType( myAjaxWidget.afterRender ) == 'function' )myAjaxWidget.afterRender( myAjaxWidget );
				
				//	Automatically bind events
				myAjaxWidget.internalBindIds( myAjaxWidget.model.target, myAjaxWidget.bindElements.internal );
				myAjaxWidget.bindClassNames( myAjaxWidget.model.target, myAjaxWidget.bindElements.className );
				myAjaxWidget.bindIds( myAjaxWidget.model.target, myAjaxWidget.bindElements.id );
				
			}
		}
	}, args ) );
	
	//	Internal reference
	myAjaxWidget.model.widget = myAjaxWidget;
	
	return  myAjaxWidget;

};